1#![allow(dead_code)]
2mod block;
11mod dnd;
12mod event;
13mod tab;
14mod window;
15mod workspace;
16
17pub use block::*;
19pub use dnd::*;
20#[allow(unused_imports)]
21pub use event::*;
22pub use tab::*;
23pub use window::*;
24pub use workspace::*;
25
26use uuid::Uuid;
27
28use super::storage::wstore::WaveStore;
29use super::storage::StoreError;
30use super::obj::*;
31
32pub const LAYOUT_ACTION_INSERT: &str = "insert";
35pub const LAYOUT_ACTION_INSERT_AT_INDEX: &str = "insertatindex";
36pub const LAYOUT_ACTION_REMOVE: &str = "remove";
37pub const LAYOUT_ACTION_CLEAR_TREE: &str = "cleartree";
38pub const LAYOUT_ACTION_REPLACE: &str = "replace";
39pub const LAYOUT_ACTION_SPLIT_HORIZONTAL: &str = "splithorizontal";
40pub const LAYOUT_ACTION_SPLIT_VERTICAL: &str = "splitvertical";
41
42pub fn ensure_initial_data(store: &WaveStore) -> Result<bool, StoreError> {
48 let clients = store.get_all::<Client>()?;
49
50 if !clients.is_empty() {
51 let client = &clients[0];
53 if client.tempoid.is_empty() {
54 let mut client = client.clone();
55 client.tempoid = Uuid::new_v4().to_string();
56 store.update(&mut client)?;
57 }
58 for window_id in &client.windowids {
60 window::check_and_fix_window(store, window_id)?;
61 }
62 return Ok(false);
63 }
64
65 let first_launch = true;
67
68 let mut client = Client {
71 oid: Uuid::new_v4().to_string(),
72 windowids: vec![],
73 tempoid: String::new(),
74 meta: MetaMapType::new(),
75 ..Default::default()
76 };
77
78 store.insert(&mut client)?;
79
80 client.tempoid = Uuid::new_v4().to_string();
82 store.update(&mut client)?;
83
84 let ws = create_workspace(store, "Starter workspace")?;
86
87 let win = create_window(store, &ws.oid)?;
89
90 client.windowids.push(win.oid.clone());
92 store.update(&mut client)?;
93
94 let tab = create_tab_with_opts(store, &ws.oid, "", true)?;
96
97 let mut agent_meta = MetaMapType::new();
114 agent_meta.insert("view".to_string(), serde_json::json!("agent"));
115 let agent_block = create_block(store, &tab.oid, agent_meta)?;
116
117 let mut sysinfo_meta = MetaMapType::new();
118 sysinfo_meta.insert("view".to_string(), serde_json::json!("sysinfo"));
119 let sysinfo_block = create_block(store, &tab.oid, sysinfo_meta)?;
120
121 let mut swarm_meta = MetaMapType::new();
122 swarm_meta.insert("view".to_string(), serde_json::json!("swarm"));
123 let swarm_block = create_block(store, &tab.oid, swarm_meta)?;
124
125 let agent_node_id = Uuid::new_v4().to_string();
128 let sysinfo_node_id = Uuid::new_v4().to_string();
129 let swarm_node_id = Uuid::new_v4().to_string();
130 let right_col_id = Uuid::new_v4().to_string();
131 let root_id = Uuid::new_v4().to_string();
132
133 let rootnode = LayoutNode {
135 id: root_id,
136 flex_direction: FlexDirection::Row,
137 size: 10.0,
138 data: None,
139 children: vec![
140 LayoutNode {
141 id: agent_node_id.clone(),
142 flex_direction: FlexDirection::Column,
143 size: 5.0,
144 children: Vec::new(),
145 data: Some(LayoutNodeData {
146 block_id: agent_block.oid.clone(),
147 ..Default::default()
148 }),
149 ..Default::default()
150 },
151 LayoutNode {
152 id: right_col_id,
153 flex_direction: FlexDirection::Column,
154 size: 5.0,
155 children: vec![
156 LayoutNode {
157 id: sysinfo_node_id.clone(),
158 flex_direction: FlexDirection::Row,
159 size: 2.0,
160 children: Vec::new(),
161 data: Some(LayoutNodeData {
162 block_id: sysinfo_block.oid.clone(),
163 ..Default::default()
164 }),
165 ..Default::default()
166 },
167 LayoutNode {
168 id: swarm_node_id.clone(),
169 flex_direction: FlexDirection::Row,
170 size: 8.0,
171 children: Vec::new(),
172 data: Some(LayoutNodeData {
173 block_id: swarm_block.oid.clone(),
174 ..Default::default()
175 }),
176 ..Default::default()
177 },
178 ],
179 data: None,
180 ..Default::default()
181 },
182 ],
183 ..Default::default()
184 };
185
186 let mut layout = store.must_get::<LayoutState>(&tab.layoutstate)?;
187 layout.rootnode = Some(rootnode);
188 layout.focusednodeid = agent_node_id.clone();
189 layout.leaforder = Some(vec![
190 LeafOrderEntry { nodeid: agent_node_id, blockid: agent_block.oid.clone() },
191 LeafOrderEntry { nodeid: sysinfo_node_id, blockid: sysinfo_block.oid.clone() },
192 LeafOrderEntry { nodeid: swarm_node_id, blockid: swarm_block.oid.clone() },
193 ]);
194 layout.pendingbackendactions = None;
195 store.update(&mut layout)?;
196
197 Ok(first_launch)
198}
199
200pub fn get_client(store: &WaveStore) -> Result<Client, StoreError> {
202 let clients = store.get_all::<Client>()?;
203 clients.into_iter().next().ok_or(StoreError::NotFound)
204}
205
206#[cfg(test)]
211mod tests {
212 use super::*;
213
214 fn make_store() -> WaveStore {
215 WaveStore::open_in_memory().unwrap()
216 }
217
218 #[test]
219 fn test_ensure_initial_data_first_launch() {
220 let store = make_store();
221 let first = ensure_initial_data(&store).unwrap();
222 assert!(first);
223
224 let client = get_client(&store).unwrap();
226 assert_eq!(client.windowids.len(), 1);
227 assert!(!client.tempoid.is_empty());
228
229 let windows = store.get_all::<Window>().unwrap();
230 assert_eq!(windows.len(), 1);
231 assert_eq!(windows[0].pos.x, 0);
233 assert_eq!(windows[0].pos.y, 0);
234 assert_eq!(windows[0].winsize.width, 0);
235 assert_eq!(windows[0].winsize.height, 0);
236
237 let workspaces = store.get_all::<Workspace>().unwrap();
238 assert_eq!(workspaces.len(), 1);
239 assert_eq!(workspaces[0].name, "Starter workspace");
240 assert_eq!(workspaces[0].pinnedtabids.len(), 1);
242 assert_eq!(workspaces[0].tabids.len(), 0);
243
244 let tabs = store.get_all::<Tab>().unwrap();
245 assert_eq!(tabs.len(), 1);
246 assert_eq!(tabs[0].name, "tab1");
250 }
251
252 #[test]
253 fn test_ensure_initial_data_idempotent() {
254 let store = make_store();
255 let first = ensure_initial_data(&store).unwrap();
256 assert!(first);
257
258 let second = ensure_initial_data(&store).unwrap();
259 assert!(!second);
260
261 assert_eq!(store.count::<Client>().unwrap(), 1);
263 }
264
265 #[test]
266 fn test_create_and_delete_workspace() {
267 let store = make_store();
268 let ws = create_workspace(&store, "Test WS").unwrap();
269 assert_eq!(ws.name, "Test WS");
270
271 let t1 = create_tab(&store, &ws.oid).unwrap();
273 let t2 = create_tab(&store, &ws.oid).unwrap();
274
275 let ws = get_workspace(&store, &ws.oid).unwrap();
276 assert_eq!(ws.tabids.len(), 2);
277
278 let t1_oid = t1.oid.clone();
280 let t2_oid = t2.oid.clone();
281 delete_workspace(&store, &ws.oid).unwrap();
282 assert!(store.get::<Workspace>(&ws.oid).unwrap().is_none());
283 assert!(store.get::<Tab>(&t1_oid).unwrap().is_none());
284 assert!(store.get::<Tab>(&t2_oid).unwrap().is_none());
285 }
286
287 #[test]
288 fn test_create_and_delete_tab() {
289 let store = make_store();
290 let ws = create_workspace(&store, "WS").unwrap();
291 let tab1 = create_tab(&store, &ws.oid).unwrap();
292 let tab2 = create_tab(&store, &ws.oid).unwrap();
293
294 let ws = get_workspace(&store, &ws.oid).unwrap();
295 assert_eq!(ws.tabids.len(), 2);
296 assert_eq!(ws.activetabid, tab1.oid);
297
298 delete_tab(&store, &ws.oid, &tab1.oid).unwrap();
300 let ws = get_workspace(&store, &ws.oid).unwrap();
301 assert_eq!(ws.tabids.len(), 1);
302 assert_eq!(ws.activetabid, tab2.oid);
303 }
304
305 #[test]
306 fn test_set_active_tab() {
307 let store = make_store();
308 let ws = create_workspace(&store, "WS").unwrap();
309 let _tab1 = create_tab(&store, &ws.oid).unwrap();
310 let tab2 = create_tab(&store, &ws.oid).unwrap();
311
312 set_active_tab(&store, &ws.oid, &tab2.oid).unwrap();
313 let ws = get_workspace(&store, &ws.oid).unwrap();
314 assert_eq!(ws.activetabid, tab2.oid);
315
316 let result = set_active_tab(&store, &ws.oid, "nonexistent");
318 assert!(result.is_err());
319 }
320
321 #[test]
322 fn test_create_and_delete_block() {
323 let store = make_store();
324 let ws = create_workspace(&store, "WS").unwrap();
325 let tab = create_tab(&store, &ws.oid).unwrap();
326
327 let mut meta = MetaMapType::new();
328 meta.insert("view".to_string(), serde_json::json!("term"));
329 let block = create_block(&store, &tab.oid, meta).unwrap();
330
331 let tab = store.must_get::<Tab>(&tab.oid).unwrap();
332 assert_eq!(tab.blockids.len(), 1);
333 assert_eq!(tab.blockids[0], block.oid);
334
335 let loaded = store.must_get::<Block>(&block.oid).unwrap();
336 assert_eq!(loaded.parentoref, format!("tab:{}", tab.oid));
337 assert_eq!(loaded.meta.get("view").unwrap(), "term");
338
339 delete_block(&store, &tab.oid, &block.oid).unwrap();
340 assert!(store.get::<Block>(&block.oid).unwrap().is_none());
341 let tab = store.must_get::<Tab>(&tab.oid).unwrap();
342 assert!(tab.blockids.is_empty());
343 }
344
345 #[test]
346 fn test_create_and_close_window() {
347 let store = make_store();
348 ensure_initial_data(&store).unwrap();
349
350 let client = get_client(&store).unwrap();
351 let initial_count = client.windowids.len();
352
353 let ws = create_workspace(&store, "WS2").unwrap();
354 let window = create_window(&store, &ws.oid).unwrap();
355
356 let mut client = get_client(&store).unwrap();
358 client.windowids.push(window.oid.clone());
359 store.update(&mut client).unwrap();
360
361 close_window(&store, &window.oid).unwrap();
362 let client = get_client(&store).unwrap();
363 assert_eq!(client.windowids.len(), initial_count);
364 assert!(store.get::<Window>(&window.oid).unwrap().is_none());
365 }
366
367 #[test]
368 fn test_focus_window() {
369 let store = make_store();
370 ensure_initial_data(&store).unwrap();
371
372 let ws = create_workspace(&store, "WS2").unwrap();
373 let w2 = create_window(&store, &ws.oid).unwrap();
374 let mut client = get_client(&store).unwrap();
375 client.windowids.push(w2.oid.clone());
376 store.update(&mut client).unwrap();
377
378 focus_window(&store, &w2.oid).unwrap();
380 let client = get_client(&store).unwrap();
381 assert_eq!(client.windowids[0], w2.oid);
382 }
383
384 #[test]
385 fn test_switch_workspace() {
386 let store = make_store();
387 ensure_initial_data(&store).unwrap();
388
389 let client = get_client(&store).unwrap();
390 let window_id = &client.windowids[0];
391 let window = store.must_get::<Window>(window_id).unwrap();
392 let old_ws = window.workspaceid.clone();
393
394 let new_ws = create_workspace(&store, "New WS").unwrap();
395 switch_workspace(&store, window_id, &new_ws.oid).unwrap();
396
397 let window = store.must_get::<Window>(window_id).unwrap();
398 assert_eq!(window.workspaceid, new_ws.oid);
399 assert_ne!(window.workspaceid, old_ws);
400 }
401
402 #[test]
403 fn test_resolve_block_id_prefix() {
404 let store = make_store();
405 let ws = create_workspace(&store, "WS").unwrap();
406 let tab = create_tab(&store, &ws.oid).unwrap();
407
408 let meta = MetaMapType::new();
409 let block = create_block(&store, &tab.oid, meta).unwrap();
410 let prefix = &block.oid[..8];
411
412 let resolved = resolve_block_id_from_prefix(&store, &tab.oid, prefix).unwrap();
413 assert_eq!(resolved, block.oid);
414
415 let result = resolve_block_id_from_prefix(&store, &tab.oid, "00000000");
417 assert!(result.is_err());
418 }
419
420 #[test]
421 fn test_list_workspaces() {
422 let store = make_store();
423 create_workspace(&store, "WS1").unwrap();
424 create_workspace(&store, "WS2").unwrap();
425 create_workspace(&store, "WS3").unwrap();
426
427 let all = list_workspaces(&store).unwrap();
428 assert_eq!(all.len(), 3);
429 }
430
431 #[test]
432 fn test_check_and_fix_window_missing_workspace() {
433 let store = make_store();
434 let mut window = Window {
436 oid: Uuid::new_v4().to_string(),
437 workspaceid: "nonexistent".to_string(),
438 pos: Point { x: 0, y: 0 },
439 winsize: WinSize {
440 width: 800,
441 height: 600,
442 },
443 meta: MetaMapType::new(),
444 ..Default::default()
445 };
446 store.insert(&mut window).unwrap();
447
448 window::check_and_fix_window(&store, &window.oid).unwrap();
449
450 let fixed = store.must_get::<Window>(&window.oid).unwrap();
452 assert_ne!(fixed.workspaceid, "nonexistent");
453 let ws = store.must_get::<Workspace>(&fixed.workspaceid).unwrap();
454 assert_eq!(ws.tabids.len(), 1); }
456
457 #[test]
458 fn test_move_block_to_tab() {
459 let store = make_store();
460 let ws = create_workspace(&store, "WS").unwrap();
461 let tab1 = create_tab(&store, &ws.oid).unwrap();
462 let tab2 = create_tab(&store, &ws.oid).unwrap();
463
464 let meta = MetaMapType::new();
465 let block = create_block(&store, &tab1.oid, meta).unwrap();
466
467 let t1 = store.must_get::<Tab>(&tab1.oid).unwrap();
469 assert_eq!(t1.blockids.len(), 1);
470 assert_eq!(t1.blockids[0], block.oid);
471
472 move_block_to_tab(&store, &block.oid, &tab1.oid, &tab2.oid, &ws.oid, false).unwrap();
474
475 let t1 = store.must_get::<Tab>(&tab1.oid).unwrap();
477 let t2 = store.must_get::<Tab>(&tab2.oid).unwrap();
478 assert!(t1.blockids.is_empty());
479 assert_eq!(t2.blockids.len(), 1);
480 assert_eq!(t2.blockids[0], block.oid);
481
482 let b = store.must_get::<Block>(&block.oid).unwrap();
484 assert_eq!(b.parentoref, format!("tab:{}", tab2.oid));
485 }
486
487 #[test]
488 fn test_move_block_to_tab_auto_close() {
489 let store = make_store();
490 let ws = create_workspace(&store, "WS").unwrap();
491 let tab1 = create_tab(&store, &ws.oid).unwrap();
492 let tab2 = create_tab(&store, &ws.oid).unwrap();
493
494 let block = create_block(&store, &tab1.oid, MetaMapType::new()).unwrap();
495
496 move_block_to_tab(&store, &block.oid, &tab1.oid, &tab2.oid, &ws.oid, true).unwrap();
498
499 assert!(store.get::<Tab>(&tab1.oid).unwrap().is_none());
501
502 let ws = store.must_get::<Workspace>(&ws.oid).unwrap();
504 assert_eq!(ws.tabids.len(), 1);
505 assert_eq!(ws.tabids[0], tab2.oid);
506 }
507
508 #[test]
509 fn test_move_block_same_tab_noop() {
510 let store = make_store();
511 let ws = create_workspace(&store, "WS").unwrap();
512 let tab = create_tab(&store, &ws.oid).unwrap();
513 let block = create_block(&store, &tab.oid, MetaMapType::new()).unwrap();
514
515 move_block_to_tab(&store, &block.oid, &tab.oid, &tab.oid, &ws.oid, false).unwrap();
517
518 let t = store.must_get::<Tab>(&tab.oid).unwrap();
519 assert_eq!(t.blockids.len(), 1);
520 }
521
522 #[test]
523 fn test_promote_block_to_tab() {
524 let store = make_store();
525 let ws = create_workspace(&store, "WS").unwrap();
526 let tab = create_tab(&store, &ws.oid).unwrap();
527 let block = create_block(&store, &tab.oid, MetaMapType::new()).unwrap();
528
529 let new_tab = promote_block_to_tab(&store, &block.oid, &tab.oid, &ws.oid, false).unwrap();
531
532 let old_tab = store.must_get::<Tab>(&tab.oid).unwrap();
534 assert!(old_tab.blockids.is_empty());
535
536 let nt = store.must_get::<Tab>(&new_tab.oid).unwrap();
538 assert_eq!(nt.blockids.len(), 1);
539 assert_eq!(nt.blockids[0], block.oid);
540
541 let ws = store.must_get::<Workspace>(&ws.oid).unwrap();
543 assert_eq!(ws.tabids.len(), 2);
544
545 assert_eq!(ws.activetabid, new_tab.oid);
547 }
548
549 #[test]
550 fn test_reorder_tab() {
551 let store = make_store();
552 let ws = create_workspace(&store, "WS").unwrap();
553 let tab1 = create_tab(&store, &ws.oid).unwrap();
554 let tab2 = create_tab(&store, &ws.oid).unwrap();
555 let tab3 = create_tab(&store, &ws.oid).unwrap();
556
557 let ws_data = store.must_get::<Workspace>(&ws.oid).unwrap();
559 assert_eq!(ws_data.tabids, vec![tab1.oid.clone(), tab2.oid.clone(), tab3.oid.clone()]);
560
561 reorder_tab(&store, &ws.oid, &tab3.oid, 0).unwrap();
563
564 let ws_data = store.must_get::<Workspace>(&ws.oid).unwrap();
565 assert_eq!(ws_data.tabids, vec![tab3.oid.clone(), tab1.oid.clone(), tab2.oid.clone()]);
566
567 reorder_tab(&store, &ws.oid, &tab1.oid, 99).unwrap();
569
570 let ws_data = store.must_get::<Workspace>(&ws.oid).unwrap();
571 assert_eq!(ws_data.tabids, vec![tab3.oid.clone(), tab2.oid.clone(), tab1.oid.clone()]);
572 }
573
574 #[test]
575 fn test_move_tab_to_workspace() {
576 let store = make_store();
577 let ws1 = create_workspace(&store, "WS1").unwrap();
578 let ws2 = create_workspace(&store, "WS2").unwrap();
579 let tab1 = create_tab(&store, &ws1.oid).unwrap();
580 let tab2 = create_tab(&store, &ws1.oid).unwrap();
581 let tab3 = create_tab(&store, &ws2.oid).unwrap();
582
583 set_active_tab(&store, &ws1.oid, &tab1.oid).unwrap();
585
586 move_tab_to_workspace(&store, &tab2.oid, &ws1.oid, &ws2.oid, None).unwrap();
588
589 let ws1_data = store.must_get::<Workspace>(&ws1.oid).unwrap();
590 let ws2_data = store.must_get::<Workspace>(&ws2.oid).unwrap();
591
592 assert_eq!(ws1_data.tabids, vec![tab1.oid.clone()]);
593 assert!(ws2_data.tabids.contains(&tab2.oid));
594 assert!(ws2_data.tabids.contains(&tab3.oid));
595 assert_eq!(ws2_data.activetabid, tab2.oid);
597 }
598
599 #[test]
600 fn test_move_tab_to_workspace_last_tab_blocked() {
601 let store = make_store();
602 let ws1 = create_workspace(&store, "WS1").unwrap();
603 let ws2 = create_workspace(&store, "WS2").unwrap();
604 let tab1 = create_tab(&store, &ws1.oid).unwrap();
605 let _tab2 = create_tab(&store, &ws2.oid).unwrap();
606
607 let result = move_tab_to_workspace(&store, &tab1.oid, &ws1.oid, &ws2.oid, None);
609 assert!(result.is_err());
610 }
611
612 #[test]
613 fn test_tear_off_block() {
614 let store = make_store();
615 let ws = create_workspace(&store, "WS").unwrap();
616 let tab = create_tab(&store, &ws.oid).unwrap();
617 let block = create_block(&store, &tab.oid, MetaMapType::new()).unwrap();
618 let block2 = create_block(&store, &tab.oid, MetaMapType::new()).unwrap();
619
620 let new_ws = tear_off_block(&store, &block.oid, &tab.oid, &ws.oid, true).unwrap();
622
623 let tab_data = store.must_get::<Tab>(&tab.oid).unwrap();
625 assert!(!tab_data.blockids.contains(&block.oid));
626 assert!(tab_data.blockids.contains(&block2.oid));
627
628 assert!(!new_ws.oid.is_empty());
630 assert_eq!(new_ws.tabids.len(), 1);
631 let new_tab = store.must_get::<Tab>(&new_ws.tabids[0]).unwrap();
632 assert!(new_tab.blockids.contains(&block.oid));
633
634 let block_data = store.must_get::<Block>(&block.oid).unwrap();
636 assert_eq!(block_data.parentoref, format!("tab:{}", new_tab.oid));
637 }
638
639 #[test]
640 fn test_tear_off_tab() {
641 let store = make_store();
642 let ws = create_workspace(&store, "WS").unwrap();
643 let tab1 = create_tab(&store, &ws.oid).unwrap();
644 let tab2 = create_tab(&store, &ws.oid).unwrap();
645 set_active_tab(&store, &ws.oid, &tab1.oid).unwrap();
646
647 let new_ws = tear_off_tab(&store, &tab2.oid, &ws.oid).unwrap();
649
650 let ws_data = store.must_get::<Workspace>(&ws.oid).unwrap();
652 assert_eq!(ws_data.tabids, vec![tab1.oid.clone()]);
653
654 assert_eq!(new_ws.tabids, vec![tab2.oid.clone()]);
656 assert_eq!(new_ws.activetabid, tab2.oid);
657 }
658
659 #[test]
660 fn test_tear_off_last_tab_blocked() {
661 let store = make_store();
662 let ws = create_workspace(&store, "WS").unwrap();
663 let tab1 = create_tab(&store, &ws.oid).unwrap();
664
665 let result = tear_off_tab(&store, &tab1.oid, &ws.oid);
667 assert!(result.is_err());
668 }
669}